接下來進入 FP 離奇的部分了,聽說一進入這領域就會喪失跟別人解釋的能力,自己也不是這麼熟,若有錯誤或建議歡迎底下留言。
A functor is simply something that can be mapped over
這句話分成兩部分,一個是 something 一個是 can be mapped over,只要符合這兩個就是 Functor 了。
A set of values arranged in some shape
↑ The values are in yellow, and the shape is in blue
最容易懂的例子就是 Array
An array is a set (or a collection) of values,because it’s a list of values
console.log([ 2, 4, 6 ].map(x => x + 3))
// => [ 5, 7, 9 ]
不止 Array 是 Something,任何原始型別 ( string
、 number
... ) 或是 Object 甚至 Function 都是 Something
// Something 1: Array
[1,2,3,4,5] // 被存放在 Array 裡的一組值
// Something 2: Object
{ age: 22, name: 'Hannah'} // 被存在物件裡的一組值
// Something 3: Single value
39 // 某個值,除了 Number 也可以是任何型別
代表你可以把 list 中每一個值做一些事然後輸出
[ 1, 2, 3, 4, 5 ].map(x => x + 3)); // [ 4, 5, 6, 7, 8 ]
list 就是 [1, 2, 3, 4, 5, 6]
,每一個值經過 x => x + 3
這個 function 後 map 到新的 list [4, 5, 6, 7, 8]
input map 到的 output,shape and structure 會相同
注: 有人也會說輸入輸出的數據結構相同 ,但有人建議我說 JS 是弱型別,所以這樣翻很奇怪,可能用 Type 類別 (比較像 js Class 不是型別) 相同會比較適合、同義詞還有 Context 等。
Q: 以下範例是 Functor 嗎?
const inputObj = { age: 22, grade: 10} // 被存在物件裡的一組值
let outputObj = {};
for (const key of Object.keys(inputObj)) {
outputObj[key] = inputObj[key] + 1
}
// { age: 23, grade: 11}
A: 輸入是 object
輸出也是 object
,type 相同所以是 Functor
Q: 那假如像以下輸入輸出型別不一樣呢 ?
[1,2,3].map(x => x.toString()) // ['1', '2', '3',]
A: 雖然輸入型別是 number
,輸出型別是 string
並不相同,但 type 類別相同所以也是 Functor
Q: 那若像以下星星形狀 map 到圓圈是 Functor 嗎?
// 範例 3,context 不同,一個是 Array 一個是 Object 所以不是
Functor[1,2,3,4,5] -> { 1: '1', 2: '2', 3: '3', 4: '4', 5: '5'}
A: 不是的,因為他們 type (context) 不同
Functor 除了上面說的 Array、Obj、Map、Set...也都是 Functor,另外也可以長得像下圖這樣,Number 這個值包在 NumberBox 這個 data Type 下
const NumberBox = number => ({
// 這裡就是 fmap, 在 js key 通常會寫 map
fmap: fn => NumberBox(fn(number)),
value: number,
});
NumberBox
這種 一個盒子 context (shape) 裡裝著一組數字 (Value) 也符合 Something 定義,若也滿足 fmap
can be map over 特性也就是 Functor, 這個盒子相當重要,不過這邊就先有個記憶就好。
所以簡單來說 Functor
傳入一個函式改變內部的資料,但維持外殼不變
也可以用以下來檢視,因為 Functor 必須滿足
a.map(x => x) === a
a.map(x => f(g(x))) === a.map(g).map(f)
Effect.of = value => Effect(() => value)
如有錯誤或需要改進的地方,拜託跟我說。
我會以最快速度修改,感謝您
歡迎追蹤我的部落格,除了技術文也會分享一些在矽谷工作的甘苦。
Functor 這名詞,在不同語言中有不同的解釋,C++ 中也使用 Functor 這名詞,不過並不是這邊討論的 Functor。
因為這邊討論 FP,就用 Haskell 來說明,對於 Haskell 來說,Functor
這東西,簡單來說,就是一個具有 fmap
行為的型態:
class Functor type where
fmap :: (a -> b) -> (type a -> type b)
括號只是為了閱讀上便於理解而加上的,其實可以不用,簡單來說,型態 type
若實作了 fmap
的行為,它的 fmap
接受函式 a -> b
,也就是若 fmap (a -> b)
,會傳回 type a -> type b
函式,這個函式可以將 type a
轉為 type b
。
不過在 JavaScript 這門語言中,其實沒有對等的東西,只能實作類似的概念,這是為什麼從 JavaScript 語言來理解 Functor,往往充滿誤解的原因。
JavaScript 中最常被稱為 Functor 的形態是 Array
,畢竟它可以用 map
方法來模擬一下,先定義一個 fmap
:
function fmap(f) {
return array => Array.prototype.map.call(array, f);
}
有了這個 fmap
,若給他一個 x =>
`${x}`
呢?
const arr2arr = fmap(x => `${x}`);
x =>
`${x}`
是個 a -> b
的函式,a
是 number
,b 是 string
,傳回的函式指定給 arr2arr
,它是個 type a -> type b
的函式,只不過 type 在這邊是指 [],也就是說,若指定給 arr2arr 的引數是 [number],傳回的會是 [string]:
console.log(arr2arr([1])); // ['1']
就以上的模擬來說,Array
與 fmap
近似了 Functor 的概念。
嗯?fmap
不是指 flatMap
嗎?不是!JavaScript 中特定物件的 flatMap,其實是 Monad 的概念。
在上面是為了容易對照 type a -> type b
,採取了 [1] -> ['1'],其實也可以 [1, 2, 3] -> ['1', '2', '3']:
console.log(arr2arr([1, 2, 3])); // ['1', '2', '3']
簡單來說,就是 [numbers] -> [strings] 的意思。
因此,JavaScript 中最類似 Functor 的實作之一是 Array
,因為它可以用 map
方法來模擬;那麼你的這個範例是 Functor 嗎?不是!
const inputObj = { age: 22, grade: 10} // 被存在物件裡的一組值
let outputObj = {};
for (const key of Object.keys(inputObj)) {
outputObj[key] = inputObj[key] + 1
}
不能說是,然而如果你這麼做:
function fmap(mapper) {
return o => {
let outputObj = {};
for(const key of Object.keys(o)) {
outputObj[key] = mapper(o[key]);
}
return outputObj;
};
}
const o2o = fmap(v => v + 1);
console.log(o2o({age: 22, grade: 10})); // { age: 23, grade: 11 }
那我會說 Object
與 fmap
近似了 Functor 的概念,fmap(v => v + 1)
可以接受 number
-> number
,傳回 Object(number)
-> Object(number)
。
因此這句話「Functor 除了上面說的 Array、Obj、Map、Set...也都是 Functor」作為結論是有待商確的,若這是結論,我只要能實作出接受 a -> b
,傳回 type a -> type b
,那任何在 JavaScript 中的形態,都可以是 Functor 了。
記得,JavaScript 中沒有對等於 Functor 的實體,不能說直接稱某形態是 Functor,只能模擬出類似的概念。
難怪找尋參考資料時都覺得每篇解釋得不一樣,原來是 "JavaScript 中沒有對等於 Functor 的實體"
若是單講滿足 fmap 特性就是 Functor 那的確 JS 所有都是 Functor
全都要談的話閃不開 category,先假設 a -> b 的型別就是 object a 到 b 的 morphism
那麼 functor F 就是說你有兩個 category,讓我們用 C 跟 L 來代替,a, b 是 C 的 object 的話,那麼 F a, F b 是 L 的 object。並且必須保留 a 跟 b 的結構(morphism 映射過去),才能稱之為 functor
尷尬的是 Haskell 雖然盡可能以 category 為理論指導,但是又得兼顧身為程式語言的實用性,所以常見的 fmap 只是特殊的 functor
從 Hask category 映射到 Hask category,事實上是 endofunctor(在同一個 category 裡面的 functor)
但是更泛用的版本基本上還蠻難用的,因為只能是嵌入或是其他近似,所以還是讓 category theory 留做理論指導即可xd
不過這些倒是可以說明為什麼 Haskell 會特別說明 functor laws 並要求開發者遵循